﻿using System;
using System.Collections;
using System.Reflection;
using System.Runtime.Serialization;
using CI.Interfaces;

namespace CI.Organism
{
    /// <summary>Base gene class</summary>
    [DataContract]
    public abstract class Gene
    {
        #region Properties

        /// <summary>Gene's GUID used to uniquely identify it in the chromosome </summary>
        [DataMember]
        public Guid Guid { get; set; }

        /// <summary>the Organism(current) the genes will be acting on</summary>
        [DataMember]
        public IOrganism Organism { get; set; }

        /// <summary>Action Type</summary>
        /// todo : not sure if this is applicable at the gene level as it really only applies to cellular or structal cells
        /// and not really to say reproductive genes
        [DataMember]
        public ActionType ActionType { get; set; }

        /// <summary>If the Gene action type has been actioned then don't process it</summary>
        public bool Actioned { get; set; }

        /// <summary>The list of requirements to forefill to action the gene</summary>
        [DataMember]
        public IRequirementCollection Requirements { get; set; }

        /// <summary>
        /// Base gene class
        /// </summary>
        /// <param name="organism">Organism the gene belongs to</param>
        /// <param name="guid">Guid</param>
        public Gene(IOrganism organism, Guid guid)
        {
            #region Argument check
            if (!(organism is IOrganism))
                throw new ArgumentException("Organism");
            if (guid == Guid.Empty)
                throw new ArgumentException("guid");
            #endregion

            Organism = organism;
            Guid = guid;
        }

        /// <summary>
        /// Take this parents Gene and create a new Gene to be put in the new child organism
        /// Mutate the current Gene and return a new one, deep class level mutation
        /// </summary>
        /// <param name="mutationRate">mutation rate to be applied</param>
        /// <param name="childOrganism">child Organism it will be going into</param>
        /// <returns>new gene for child</returns>
        public abstract IGene Mutate(double mutationRate, IOrganism childOrganism);
        #endregion

        protected static SimEnv SimEnv { get { return SimEnv.Instance; } }

        /// <summary>Is the gene actionable, eg does it meet the requirements in the list</summary>
        public virtual bool Actionable()
        {
            // lets make sure we have something in the requirement list
            if ((Requirements == null) || (Requirements.Count == 0))
                throw new DNAException("Requirement Collection is Empty(null)");

            // check all the requirements in the list
            foreach (IRequirement requirement in Requirements)
                // if one fails no point checking the rest
                if (!requirement.State())
                    return false;

            // if we got this far they must have all passed
            return true;
        }

        /// <summary>
        /// Create a new gene
        /// </summary>
        /// <param name="mutationRate">mutation rate</param>
        /// <param name="organism">organism</param>
        /// <returns>new gene</returns>
        public static IGene CreateNewGene(double mutationRate, IOrganism organism)
        {
            #region Argument check
            if (organism == null)
                throw new ArgumentException("Organism");
            #endregion

            // mutate adding new genes
            // we need to reflect and see what genes are available
            ArrayList genes = Globals.GetConcreteClasses(typeof(IGene));
            // now create a new one

            // save its type
            var classType = (Type)genes[Mutation.CollectionSelect(genes)];
            // We are anticipating a constructor that takes 2 parameters,
            var conTypes = new Type[2];
            conTypes[0] = typeof(IOrganism);
            conTypes[1] = typeof(Guid);
            // Get the constructor that adheres to our parameters.
            ConstructorInfo constructor = classType.GetConstructor(conTypes);
            // Define the parameters to pass into our constructor when we call it.
            var constructorParams = new object[2];
            constructorParams[0] = organism;
            constructorParams[1] = Guid.NewGuid();
            SimEnv.Instance.TraceLogger.LogDebug(string.Format("Invoking constructor of type {0}", classType));

            // Invoke the constructor dynamically, getting an instance of the class.            
            var newGene = constructor.Invoke(constructorParams) as IGene;

            // we have to give it a empty requirements stack
            newGene.Requirements = new RequirementCollection();
            // mutate the requirement - this will create some if they dont exists
            newGene.Requirements = newGene.Requirements.Mutate(mutationRate, organism);

            // mutate the default gene and add it                
            newGene = newGene.Mutate(mutationRate, organism);

            return newGene;
        }

        /// <summary>Carry out the action for the gene</summary>
        public abstract void Action();

        /// <summary>
        /// do a equals comparison of one gene type to another only comparing the value types, no refference comparisons
        /// </summary>
        /// <param name="gene">gene to compare against</param>
        /// <returns>true if equal value types</returns>
        public virtual bool EqualsValueTypes(IGene gene)
        {
            // make sure we got passed one
            if (gene == null)
                return false;

            // compare gene bits
            if (ActionType != gene.ActionType)
                return false;
            if (Guid != gene.Guid)
                return false;
            if (Requirements.Count != gene.Requirements.Count)
                return false;

            // compare requirements
            foreach (IRequirement requirement in Requirements)
            {
                // find the requirement
                IRequirement foundRequirement = gene.Requirements.Find(requirement.Guid);

                // see if we found one
                if (foundRequirement == null)
                    return false;

                // compare the types
                if (!requirement.GetType().Equals(foundRequirement.GetType()))
                    return false;

                // compare them
                if (!requirement.EqualsValueTypes(foundRequirement))
                    return false;
            }

            return true;
        }

    }
}